/*
 ============================================================================
 Name        : BtInquiryRssi.c
 Author      : primianoc@gmail.com
 Version     :
 Copyright   : 
 Description : Bluetooth Inquiry with RSSI in C, Ansi-style
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <sys/poll.h>
#include <pthread.h>
#include <unistd.h>
#include "btInquiryRssi_BtInquiryRssi.h"

// struct che contiene un BDAddress sottoforma di array di caratteri,
// la classe del dispositivo sottoforma di array di interi unsigned,
// il valore cumulativo dell'RSSI sottoforma di float e il numero di
// volte che è stato rilevato. Al termine di un inquiry il valore
// dell'rssi deve essere diviso per l'intero timesDetected
typedef struct
{
	char bdaddressCharArray[18];
	uint8_t deviceClassCharArray[3];
	float rssi;
	int timesDetected;
} inq_addr_class_rssi;


/*
 * Dichiarazione variabili globali
 */

/*
 * Socket che rappresenta una connessione al micro controllore
 * presente su uno dei dispositivi bluetooth locali, selezionato
 * col metodo hci_get_route() oppure hci_devid()
 */
int socketBtHci;

/*
 * Identificativo del dispositivo bluetooth da utilizzare per
 * effetturare l'inquiry e per raccogliere i risultati.
 */
int deviceID = -1;

/*
 * Durata dell'operazione di inquiry espressa in secondi
 */
int durataInquiry = -1;

/*
 * Numero di pacchetti HCI da catturare
 * e quindi il numero massimo di dispositivi
 * bluetooth rilevabili.
 */
int maxDevicesReturned = -1;

/*
 * Variabile impostata a 0 se l'inizializzazione
 * ha avuto esito positivo, -1 altrimenti.
 */
int deviceInitialized = -1;

/*
 * Modalita' inquiry.
 */
const uint8_t inqModeRssi = 1;
const uint8_t inqModeStd = 0;


/*
 * Dichiarazione delle funzioni.
 */

/*
 * Restituisce 0 se l'inizializzazione e'
 * andata a buon fine oppure -1 se non è
 * stato possibile inzializzare il dispositivo
 * devID.
 */
int initBtDevice(int devID);

/*
 * Parametri: durata dell'inquiry espressa in secondi,
 * numero di pacchetti HCI da catturare ossia numero
 * massimo di dispositivi distinti da rilevare
 * e un puntato a puntatore di caratteri che,
 * al termine della funzione, conterrà le stringhe
 * BDAddress,RSSI. Restituisce il numero di dispositivi
 * effettivamente rilevati nell'inquiry.
 */
int inquiryRssi(const int max_time, const int max_devices, char **devicesPtr);

/*
 * Rilascia le risorse utilizzate, in particolare
 * la socketBtHci.
 */
int shutDownBtDevice();

/*
 * Effettua un inquiry senza rssi eseguita da un
 * pthread.
 */
void *inquiryNoRssi();

/*
 * Imposta il metodo di inquiry sul dispositivo
 * deviceID utilizzando la socketBtHci.
 */
int hciWriteInqMode(int inqMode);

/*
 * Restituisce 0 se l'inizializzazione e' andata
 * a buon fine oppure -1 se non è stato possibile
 * inizializzare il primo dispositivo bluetooth
 * disponibile.
 */
int initFirstBtDevice()
{
	/*
	 * Il parametro della funzione hci_get_route
	 * è l'indirizzo del dispositivo locale di tipo puntatore a baddr_t.
	 * Se indico NULL verrà utilizzato il primo dispositivo bluetooth disponibile
	 * Nota: non è detto che sia il dispositivo hci0 perchè potrebbe essere DOWN.
	 */
	return initBtDevice(hci_get_route(NULL));
}



int initBtDevice(int devID)
{
	/*
	 * Se devID è minore di zero allora
	 * c'è stato un problema e restituisco -1.
	 */
	if (devID < 0)
	{
		return -1;
	}

	deviceID = devID;

	/*
	 * La funzione hci_open_dev apre una socket HCI
	 * col dispositivo bluetooth locale corrispondente
	 * al valore passato come parametro.
	 * Tale valore viene restituito anche dal comando
	 * "hcitool dev".
	 */
	socketBtHci = hci_open_dev( deviceID );

	/*
	 * Se la socket è minore di zero allora
	 * c'è stato un problema.
	 */
	if (socketBtHci < 0)
	{
		return -1;
	}

	uint8_t modeSet = -1;

	uint8_t *ptrInt = &modeSet;

	/*
	 * Leggo la modalità di inquiry inviando un comando direttamente
	 * al micro controllore del dispositivo bluetooth utilizzando
	 * la socket HCI. Restituisce un valore diverso da zero se
	 * non è stato possibile leggere la modalità di inquiry attuale.
	 */
	int retInqMode = hci_read_inquiry_mode(socketBtHci, ptrInt, 1000);

	if(retInqMode != 0)
	{
		return -1;
	}

	if(modeSet != inqModeRssi)
	{
		retInqMode = hciWriteInqMode(inqModeRssi);

		if(retInqMode != 0)
		{
			return -1;
		}
	}

	/*
	 * struct usata per selezionare gli eventi provenienti dal layer HCI.
	 */
	struct hci_filter filterHciStruct;

	/*
	 * Azzeramento della struct filterHciStruct.
	 */
	hci_filter_clear(&filterHciStruct);

	/*
	 * Catturo i pacchetti di tipo HCI_EVENT_PKT.
	 */
	hci_filter_set_ptype(HCI_EVENT_PKT, &filterHciStruct);

	/*
	 * Catturo gli eventi di tipo EVT_INQUIRY_RESULT_WITH_RSSI
	 * che contengono la risposta da parte di un dispositivo
	 * bluetooth rilevato con il relativo RSSI.
	 */
	hci_filter_set_event(EVT_INQUIRY_RESULT_WITH_RSSI, &filterHciStruct);

	/*
	 * Catturo gli eventi di tipo EVT_INQUIRY_COMPLETE che indicano
	 * la fine dell'inquiry.
	 */
	hci_filter_set_event(EVT_INQUIRY_COMPLETE, &filterHciStruct);

	/*
	 * Imposto le caratteristiche della socket HCI aggiungendo i
	 * filtri precedentemente inseriti nella struct filterHciStruct.
	 */
	if (setsockopt(socketBtHci, SOL_HCI, HCI_FILTER, &filterHciStruct, sizeof(filterHciStruct)) < 0)
	{
		return -1;
	}

	deviceInitialized = 0;

	/*
	 * Restituisco zero per indicare che l'inizializzazione e'
	 * andata a buon fine.
	 */
	return deviceInitialized;
}

int inquiryRssi(int max_time, int max_devices, char **stringsReturnedPtr)
{
	/*
	 * Numero di dispositivi rilevati.
	 */
	int devicesDetected = 0;

	/*
	 * max_time: durata massima dell'inquiry espressa in secondi.
	 * max_devices: numero massimo di dispositivi da rilevare.
	 */
	if(max_time > 1 && max_devices > 0)
	{
		/*
		 * Assegno i parametri passati alla funzione
		 * a variabili globali perchè verranno utilizzati
		 * dal pthread.
		 */

		/*
		 * La durata totale dell'operazione inquiry è
		 * uguale al prodotto 1.28 * durataInquiry,
		 * quindi divido max_time per 1.28.
		 */
		durataInquiry = max_time / 1.28;

		/*
		 * Numero massimo di dispositivi bluetooth da ricercare.
		 */
		maxDevicesReturned = max_devices;

		/*
		 * Creo un pthread che invoca la funzione inquiryNoRssi.
		 */
		pthread_t inquiryThread;
		int creazioneThread = pthread_create(&inquiryThread, NULL, inquiryNoRssi, NULL);

		if(creazioneThread != 0)
		{
			return -1;
		}

		/*
		 * Raccolgo i risultati dell'inquiry eseguito dal
		 * pthread catturando gli eventi
		 * dalla socket HCI tramite la funzione poll.
		 */

		/*
		 * Array di caratteri che conterrà l'evento ricevuto
		 * dalla socket HCI.
		 */
		unsigned char eventHciArray[HCI_MAX_EVENT_SIZE];

		/*
		 * La struttura di tipo pollfd è il parametro principale da
		 * passare alla funzione poll e contiene il descrittore del
		 * file, gli eventi richiesti e quelli rilevati.
		 */
		struct pollfd pollFileDescriptorStruct;

		/*
		 * Gli eventi raccolti sono di tipo POLLIN.
		 */
		pollFileDescriptorStruct.events = POLLIN;

		/*
		 * Il file descriptor da controllare è la socket HCI.
		 */
		pollFileDescriptorStruct.fd = socketBtHci;

		/*
		 * Evento HCI estratto dall'array di caratteri evenHciArray.
		 */
		hci_event_hdr *eventoHdr;

		/*
		 * Intero impostato a 1 quando viene ricevuto il pacchetto
		 * di tipo EVT_INQUIRY_COMPLETE.
		 */
		int inquiryComplete = 0;

		/*
		 * Array di struct di tipo inq_addr_class_rssi
		 * (vedi typedef all'inizio del file).
		 * Ogni struct è associata ad un dispositivo rilevato.
		 */
		inq_addr_class_rssi btDevicesArray[maxDevicesReturned];

		/*
		 * Inizializzo l'array btDevicesArray a zero.
		 */
		memset(btDevicesArray, 0, sizeof (btDevicesArray));

		/*
		 * Puntatore all'area di memoria che contiene come primo byte un intero
		 * che indica il numero di struct di tipo inquiry_info_with_rssi
		 * contenute nei successivi byte. Nelle prove effettuate è stata rilevata
		 * sempre una struct per ogni evento.
		 * Poco chiaro? Vedi le istruzioni successive :-)
		 */
		unsigned char *inquiryInfoPointer ;

		while(inquiryComplete == 0)
		{
			/*
			 * Inizializzo l'array eventHciArray a zero
			 * ogni volta che ripeto il ciclo.
			 */
			memset(eventHciArray, 0, sizeof (eventHciArray));

			/*
			 * La funzione poll restituisce il numero di file
			 * descriptor con attività in caso di successo,
			 * 0 se c'è stato un timeout e -1 in caso di errore.
			 */
			if (poll(&pollFileDescriptorStruct, 1, -1) > 0)
			{
				/*
				 * La funzione read legge sizeof(eventHciArray) byte dalla socket HCI e
				 * li copia nell'array eventHciArray. Restituisce il numero di byte
				 * effettivamente letto oppure -1 in caso di errore.
				 */
				int valoreRitorno = read(socketBtHci, eventHciArray, sizeof(eventHciArray));

				/*
				 * Se l'evento letto è di tipo HCI_EVENT_PKT (primo elemento di eventHciArray)
				 * allora potrebbe essere uno degli eventi di interesse.
				 */
				if(valoreRitorno > 0 && eventHciArray[0] == HCI_EVENT_PKT)
				{
					/*
					 * L'elemento successivo è un puntatore ad una struct di tipo hci_event_hdr
					 */
					eventoHdr = (void *) (eventHciArray +1);

					switch (eventoHdr->evt)
					{

					case EVT_INQUIRY_RESULT_WITH_RSSI:

						inquiryInfoPointer = eventHciArray + (1 + HCI_EVENT_HDR_SIZE);

						/*
						 * Il primo elemento è il numero di struct che seguono. Il secondo elemento
						 * di inquiryInfoPointer e' un puntatore ad una strct di tipo inquiry_info_with_rssi
						 * che contiene il campo rssi di tipo int8_t.
						 * Ci siamo!
						 */
						inquiry_info_with_rssi *inqInfoRssi = (inquiry_info_with_rssi *)&inquiryInfoPointer[1];

						/*
						 * Escludiamo alcuni dispositivi.
						 * I dispositivi che hanno Major device class 0x02, ossia
						 * i dispositivi di classe Phone, non devono far parte dei risultati.
						 * Nota: modificare se si vogliono rilevare anche i
						 * dispositivi di classe Phone.
						 */
						int majourDeviceClass = inqInfoRssi->dev_class[1];

						/*
						 * Non ho valutato il valore esadecimale perchè 0x02
						 * coincide con 2 in decimale.
						 */
						if( majourDeviceClass != 2)
						{
							/*
							 * Estraggo dalla struct l'indirizzo del dispositivo,
							 * la classe e l'RSSI.
							 */
							char addressBtStr[18] = { 0 };
							ba2str(&(inqInfoRssi->bdaddr), addressBtStr);
							uint8_t *devClass = inqInfoRssi->dev_class;
							float rssiReaded = inqInfoRssi->rssi;

							/*
							 * L'intero found vale zero finche' non viene rilevato un bdaddress nell'array.
							 * Se il bdaddress non viene rilevato appartiene ad un dispositivo nuovo e lo
							 * aggiungo all'array.
							 */
							int found = 0;

							/*
							 * L'intero i viene usato per scandire tutti gli elementi dell'array.
							 */
							int i = 0;

							/*
							 * Il ciclo si ferma se ho scandito tutti l'array.
							 */
							while(i < devicesDetected && found == 0)
							{
								/*
								 * La funzione strcmp restituisce 0 se le stringhe confrontate
								 * sono uguali.
								 */
								if(strcmp(btDevicesArray[i].bdaddressCharArray, addressBtStr) == 0)
								{
									// trovato!
									found = 1;

									// Sommo l'rssi rilevato a quello attuale e incremento il contatore
									// timesDetected di una unità.
									btDevicesArray[i].timesDetected = btDevicesArray[i].timesDetected + 1;
									btDevicesArray[i].rssi = btDevicesArray[i].rssi + rssiReaded;
								}

								i = i+1;
							}

							if(found == 0)
							{
								devicesDetected = devicesDetected + 1;

								strcpy(btDevicesArray[i].bdaddressCharArray, addressBtStr);
								btDevicesArray[i].deviceClassCharArray[0] = devClass[0];
								btDevicesArray[i].deviceClassCharArray[1] = devClass[1];
								btDevicesArray[i].deviceClassCharArray[2] = devClass[2];
								btDevicesArray[i].rssi = rssiReaded;
								btDevicesArray[i].timesDetected = btDevicesArray[i].timesDetected + 1;

							}

						}

						break;

					case EVT_INQUIRY_COMPLETE:

						// Inquiry completato.
						inquiryComplete = 1;

						break;

					default:

						break;
					}
				}


			}

		}

		/*
		 * Preparazione dei risultati: costruitsco tante stringhe quanti sono
		 * i dispositivi rilevati, contenenti il bdaddress e l'rssi.
		 */

		/*
		 * Matrice di caratteri stringsReturned[][]:
		 * il numero di righe e' pari al numero di dispositivi rilevati,
		 * il numero di colonne e' pari 17 caratteri per il BdAddress
		 * nella forma XX:XX:XX:XX:XX:XX + 1 carattere separatore ','
		 * + 3 caratteri per il valore dell'RSSI compreso
		 * approssimativamente tra 20 e 100 + 1 carattere terminatore '\0'.
		 * Aggiungo 1 caratteri disponibili in caso di rssi espresso con 4 cifre
		 */
		int stringLength = 23;
		char stringsReturned[devicesDetected][stringLength];

		memset(stringsReturned, 0, sizeof(stringsReturned));

		// indice dell'array di caratteri
		int i;

		for(i = 0; i < devicesDetected; i++ )
		{
			/*
			 * La prima stringa è il bdaddress
			 */
			strcat(stringsReturned[i], btDevicesArray[i].bdaddressCharArray);
			int rssiInt = abs((int)(btDevicesArray[i].rssi / btDevicesArray[i].timesDetected));

			/*
			 * La stringa contiene come primo carattere il separatore ','
			 * seguito dall'rssi e dal terminatore della stringa
			 */
			char rssiStr[6];

			rssiStr[0] = ',';

			/*
			 * Scrivo nella stringa il valore dell'rssi.
			 * La funzione sprintf aggiunge il terminatore.
			 */
			sprintf( &rssiStr[1],"%d", rssiInt);


			// Aggiungo la seconda parte della stringa.
			strcat(stringsReturned[i], rssiStr);

			/*
			 * Copio la stringa nell'array di stringhe
			 * stringsReturnedPtr passato come parametro
			 * della funzione.
			 */
			stringsReturnedPtr[i] = malloc(sizeof(stringsReturned[i]));
			strcpy(stringsReturnedPtr[i], stringsReturned[i]);
		}

	}
	return devicesDetected;
}

int shutDownBtDevice()
{
	/*
	 * Se il dispositivo è stato precedentemente inizializzato eseguo
	 * la funzione hci_close_dev altrimenti restituisco 0
	 */
	if(deviceInitialized == 0)
	{
		deviceInitialized = -1;

//		hciWriteInqMode(inqModeStd);

		int retValue = hci_close_dev(socketBtHci);

		if(retValue != 0)
		{
			return -1;
		}
		else
		{
			return 0;
		}
	}
	else
	{
		return 0;
	}

}

int hciWriteInqMode(int inqMode)
{

	/*
	 * Utilizzo la socket per impostare la modalita' di inquiry.
	 */
	int retInqMode = hci_write_inquiry_mode(socketBtHci, inqMode, 1000);

	if (retInqMode != 0)
	{
		char deviceIDStr[10];
		sprintf(deviceIDStr, "%d", deviceID);

		char inqModeStr[2];
		sprintf(inqModeStr, "%d", inqMode);

		char commandStr[40];
		strcpy(commandStr, "sudo hciconfig hci");
		strcat(commandStr, deviceIDStr);
		strcat(commandStr, " inqmode ");
		strcat(commandStr, inqModeStr);

		printf("\nNon è stato possibile impostare \n"
				"la modalita' di inquiry perchè sono \n"
				"necessari i diritti di super user. \n"
				"Eseguo il comando %s", commandStr);

		int retValue = system(commandStr);

		if(retValue != 0)
		{
			/*
			 * Non è stato possibile impostare la modalità
			 * di inquiry.
			 */
			return -1;
		}
		else
		{
			return 0;
		}
	}
	else
	{
		return 0;
	}
}

/*
 * Funzione eseguita dal thread che effettua
 * l'inquiry mentre il processo principale
 * cattura gli eventi della socket HCI.
 */
void *inquiryNoRssi()

{
	if(maxDevicesReturned > 0 && durataInquiry > 0)
	{
		int devicesReturned = 0;

		/*
		 * inquiry_info è una struct che contiene 6 campi,
		 * i più importanti dei quali sono l'indirizzo bluetooth
		 * del dispositivo rilevato, nel formato baddr_t, e la
		 * classe del dispositivo, ad esempio mouse, tastiera,
		 * modem, pc, ecc.
		 */
		inquiry_info *inquiryInfoStruct = NULL;

		/*
		 * La funzione malloc riserva una parte della memoria per
		 * contenere l'array di struct di tipo inquiry_info.
		 */
		inquiryInfoStruct = (inquiry_info*)malloc(maxDevicesReturned * sizeof(inquiry_info));

		/*
		 * Imposto a zero inquiryInfoStruct
		 */
		memset(inquiryInfoStruct, 0, sizeof(inquiryInfoStruct));

		/*
		 * Se la variabile flagCacheFlush viene settata a IREQ_CACHE_FLUSH
		 *  allora la cache che conteneva i dispositivi rilevati nel precedente
		 *  inquiry viene svuotata.
		 */
		int flagCacheFlush = IREQ_CACHE_FLUSH;

		/*
		 *  La funzione hci_inquiry prende come parametri il deviceID
		 *  del dispositivo bluetooth locale, il valore da moltiplicare
		 *  per 1,28 secondi, il numero massimo di dispositivi da restituire,
		 *  un puntatore a carattere costante, l'indirizzo dell'array di struct
		 *  e il flag per cancellare la cache dei vecchi dispositivi rilevati.
		 *  Restituisce il numero di dispositivi effettivamente rilevati nelle vicinanze.
		 *  Il quarto parametro è un puntatore a uint8_t e corrisponde al
		 *  lap, lower address part, che determina l'inquiry hopping sequence.
		 *  Tale parametro viene impostato a NULL e corrisponde al GIAC,
		 *  General Inquiry Access Code, che permette di ricercare qualsiasi
		 *  dispositivo bluetooth nelle vicinanze.
		 */
		devicesReturned = hci_inquiry(deviceID, durataInquiry, maxDevicesReturned, NULL, &inquiryInfoStruct, flagCacheFlush);

		/*
		 * Rilascio la memoria allocata
		 */
		free(inquiryInfoStruct);

	}

	return NULL;
}

JNIEXPORT jboolean JNICALL Java_btInquiryRssi_BtInquiryRssi_init__
(JNIEnv * env, jobject obj)
{
	int initReturnValue = initFirstBtDevice();

	/*
	 * Il valore di ritorno e' zero se non ci sono stati
	 * errori oppure un valore diverso da zero, ossia il
	 * codice di errore, se l'inizializzazione non è stata
	 * effettuata correttamente.
	 */
	if(initReturnValue == 0)
	{
		return JNI_TRUE;
	}
	else
	{
		return JNI_FALSE;
	}
}

JNIEXPORT jboolean JNICALL Java_btInquiryRssi_BtInquiryRssi_init__I
  (JNIEnv * env, jobject obj, jint devID)
{
	int initReturnValue = initBtDevice(devID);

	/*
	 * Il valore di ritorno e' zero se non ci sono stati
	 * errori oppure un valore diverso da zero, ossia il
	 * codice di errore, se l'inizializzazione non è stata
	 * effettuata correttamente.
	 */
	if(initReturnValue == 0)
	{
		return JNI_TRUE;
	}
	else
	{
		return JNI_FALSE;
	}
}


JNIEXPORT jobjectArray JNICALL Java_btInquiryRssi_BtInquiryRssi_inquiry
(JNIEnv *env, jobject obj, jint maxTime, jint maxDevices)
{
	if(deviceInitialized == 0)
	{
		char **stringsPointer;
		stringsPointer = malloc(maxDevices * sizeof(*stringsPointer));

		int numberDevicesReturned = inquiryRssi(maxTime, maxDevices, stringsPointer);

		jobjectArray arrayString;

		arrayString = (jobjectArray)(*env)->NewObjectArray(env, numberDevicesReturned,
				(*env)->FindClass(env, "java/lang/String"), (*env)->NewStringUTF(env, ""));

		if(numberDevicesReturned != 0)
		{
			//ho rilevato più di un dispositivo, costruisco l'array e lo restituisco al chiamante
			int i;
			for(i = 0; i < numberDevicesReturned; i++ )
			{
				(*env)->SetObjectArrayElement(env, arrayString,i,(*env)->NewStringUTF(env, stringsPointer[i]));
			}
		}

		free(stringsPointer);

		return(arrayString);
	}
	else
	{
		// Errore: non è stata invocata la funzione init()
		return NULL;
	}


}

JNIEXPORT jboolean JNICALL Java_btInquiryRssi_BtInquiryRssi_shutDown
(JNIEnv * env, jobject obj)
{
	int shutDownReturnValue = shutDownBtDevice();

	/*
	 * Il valore di ritorno e' zero se non ci sono stati
	 * errori oppure un valore diverso da zero
	 * se l'inizializzazione non è stata
	 * effettuata correttamente.
	 */
	if(shutDownReturnValue == 0)
	{
		return JNI_TRUE;
	}
	else
	{
		return JNI_FALSE;
	}
}
